# -*- coding: utf-8 -*-
"""
态势感知
"""
__authur__ = 'Yu XH'

import copy
import threading

from myAI.attdamage.handle_shoot import *
from myAI.hexmap.handle_map import *


class Situation(object):
    def __init__(self, myAgent):
        self.__color = myAgent.color
        self.__map_size = myAgent.get_map_size()
        n_hexes = self.__map_size['row'] * self.__map_size['col']
        self.our_oprs, self.basic_enemies = self.partition_operators(myAgent.scenario_info[-1])
        self.dead_enemies = []
        self.enemy_bullets = {}  # 保存敌方算子的剩余弹药数量的信息
        self.init_enemy_bullets(myAgent.scenario_info[-1])
        self.enemies = []
        self.enemy_threats = []

        self.b_threat_run = False  # 标记线程是否启动
        # self.thread_dict = {}  # 与敌方算子相关的线程字典
        self.enemy_threat = np.zeros((4, n_hexes), dtype=float)
        self.artillery_threat = np.zeros(n_hexes, dtype=float)
        # self.last_observation = myAgent.scenario_info[-1]  # 保存上一步状态，便于和当前状态比较
        self.last_step = -1
        self.situ_observation = None

        self.proximate_2_cities = self.calc_proximate_cities(myAgent)
        self.max_spread_radius = {BopType.Vehicle: 5, BopType.Infantry: 3,
                                  BopType.Aircraft: 10}  # 限定敌方算子不可无限制扩散，此值给出了最大扩散半径
        for enemy in self.basic_enemies:
            et = EnemyThreat(enemy, self, myAgent)
            self.enemy_threats.append(et)
            self.enemies.append(et.enemy_info)

        self.enemies_copy = copy.deepcopy(self.enemies)
        self.enemy_threat_copy = copy.deepcopy(self.enemy_threat)

    def update(self, input_queue, output_queue, input_queue_lock, output_queue_lock, mutex_basic_situ, mutex_threat_situ, enemy_thread_dict, myAgent):
        """更新态势信息"""
        while True:
            # 如果队列已满，则从队列中取出传入的态势对象（基本）
            # print("perceive situation update begin")
            input_queue_lock.acquire()
            if input_queue.full():
                input_situ = input_queue.get()
                input_queue_lock.release()
                # print(f'--- perceive get input_queue: {input_situ.situ_observation["time"]["cur_step"]}')
                if input_situ.situ_observation is None:
                    # print("input_situ.situ_observation is None")
                    break
                self.situ_observation = input_situ.situ_observation
                if self.situ_observation['time']['cur_step'] <= self.last_step:
                    # time.sleep(0.05)
                    continue

                # 用于查看态势子线程中是否存在跳帧的现象
                # print(f'    situation current step: {myAgent.observation["time"]["cur_step"]}')


                # 将传入的态势对象（基本）相关属性拷贝给进程中的态势对象situ
                # === 加锁 - out situation ===
                # print("myAgent.agenda.ipcArgs.mutex_basic_situ.acquire()")
                mutex_basic_situ.acquire()
                self.our_oprs = copy.deepcopy(input_situ.our_oprs)
                self.dead_enemies = copy.deepcopy(input_situ.dead_enemies)
                self.basic_enemies = copy.deepcopy(input_situ.basic_enemies)
                self.enemy_bullets = copy.deepcopy(input_situ.enemy_bullets)
                # === 释放 - out situation ===
                mutex_basic_situ.release()
                # print("myAgent.agenda.ipcArgs.mutex_basic_situ.release()")

                # 根据死亡敌方算子self.dead_enemies，更新self.enemies
                for dead_id in self.dead_enemies:
                    enemy_info = self.getEnemyInfoByID(dead_id)
                    if enemy_info:
                        self.enemies.remove(enemy_info)
                    enemy_threat = self.get_enemy_threat_by_id(dead_id)
                    if enemy_threat:
                        self.enemy_threats.remove(enemy_threat)
                    if dead_id in enemy_thread_dict.keys():
                        # 如果被杀死的线程中正在使用锁，要将线程中使用的所有锁释放，否则会阻塞父线程
                        # 最好的方式是当敌方算子死亡时，让线程自己退出
                        enemy_thread_dict.pop(dead_id)
                        print(f'enemy thread exit: {dead_id} is dead!')

                # 开启更新每个敌方算子威胁的线程
                if not self.b_threat_run:
                    for enemy_threat in self.enemy_threats:
                        if enemy_threat.enemy_info['operator']['sub_type'] != BopType.Artillery:  # 炮兵不做更新
                            enemy_id = enemy_threat.obj_id
                            # 将进程中的态势对象situ传入子线程中，用于更新敌方算子威胁
                            enemy_thread_dict[enemy_id] = threading.Thread(target=enemy_threat.update,
                                                                                   args=[self, mutex_basic_situ, mutex_threat_situ, enemy_thread_dict, myAgent])
                            enemy_thread_dict[enemy_id].start()
                    self.b_threat_run = True


                # 将子线程中更新的敌方算子威胁拷贝到进程态势对象situ中
                # === 加锁 in - enemy_threat ===
                # print("myAgent.agenda.ipcArgs.mutex_threat_situ.acquire()")
                mutex_threat_situ.acquire()
                self.enemies = []  # 更新enemies
                for enemy_threat in self.enemy_threats:
                    enemy_info = copy.deepcopy(enemy_threat.enemy_info_copy)
                    self.enemies.append(enemy_info)
                # === 释放 in - enemy_threat ===
                mutex_threat_situ.release()
                # print("myAgent.agenda.ipcArgs.mutex_threat_situ.release()")

                # 统一更新进程态势对象situ的敌方威胁属性
                self.update_enemy_threat()

                # 将更新后的进程态势对象situ放到输出队列中，
                # 如果输出队列已满，则将队列中旧的态势对象situ删除，放入最新的态势对象
                output_queue_lock.acquire()
                # print(f'【1】perceive output_situ.enemies: {len(self.enemies)}')
                if output_queue.full():
                    output_queue.get()
                    # print(f'【2】perceive output_situ.enemies: {len(output_situ.enemies)}')
                self.enemies_copy = copy.deepcopy(self.enemies)
                self.enemy_threat_copy = copy.deepcopy(self.enemy_threat)
                output_queue.put(self)
                # print(f'【3】perceive output_situ.enemies: {len(self.enemies)}')
                output_queue_lock.release()
                # print(f'+++ perceive put output_queue: {self.situ_observation["time"]["cur_step"]}')

                self.last_step = self.situ_observation['time']['cur_step']
            else:
                input_queue_lock.release()

            # 让子进程自己退出
            # if self.last_step == myAgent.max_step:
            # 最后5个step不再放入更新的态势数据，便于子进程先于主进程结束
            if (self.last_step >= myAgent.max_step-5) and (self.last_step <= myAgent.max_step):
                # 将剩余敌方算子子线程全部退出
                enemy_thread_dict.clear()
                # time.sleep(0.5)
                break
            # print("perceive situation update complete")
            time.sleep(0.05)
        print(f'game over: enemy_threat_process exit')


    def partition_operators(self, observation):
        """区分我方和敌方算子"""
        our_oprs = []
        enemies = []
        if observation['operators']:
            for opr in observation['operators']:
                if opr['color'] == self.__color and opr['lose_control'] == 0:  # 失去控制的算子不算是有效算子
                    our_oprs.append(opr)
                elif opr['lose_control'] == 0:
                    enemies.append(opr)
        if observation['passengers']:
            for opr in observation['passengers']:  # 敌方的车上算子是看不到的
                if opr['color'] == self.__color:
                    our_oprs.append(opr)
                else:
                    enemies.append(opr)
        return our_oprs, enemies

    def getEnemyInfoByID(self, obj_id):
        for enemy_info in self.enemies:
            if enemy_info['obj_id'] == obj_id:
                return enemy_info
        return None

    def get_enemy_threat_by_id(self, obj_id):
        for enemy_threat in self.enemy_threats:
            if enemy_threat.obj_id == obj_id:
                return enemy_threat
        return None

    def get_dead_enemies(self, observation, last_observation):
        """ 找出死亡的敌方算子"""
        if observation:
            for obj in observation['operators']:
                if obj['color'] != self.__color and obj['lose_control'] == 1:  # 失去控制的敌方算子
                    self.dead_enemies.append(obj['obj_id'])
            if observation['judge_info']:
                for enemy_info in self.enemies:
                    operator = enemy_info['operator']
                    damage = 0
                    for judge_info in observation['judge_info']:
                        obj_id = judge_info["target_obj_id"]
                        if obj_id == operator['obj_id']:
                            if operator['type'] == BopType.Infantry and operator['keep'] == 1 and judge_info[
                                "damage"] == 0:
                                damage += 1
                            else:
                                damage += max(judge_info["damage"], 0)
                        if operator['sub_type'] == BopType.CruiseMissile:  # 巡飞弹射击的判断
                            if operator['obj_id'] == judge_info['att_obj_id']:
                                self.dead_enemies.append(operator['obj_id'])
                    if operator['blood'] <= damage:
                        self.dead_enemies.append(operator['obj_id'])

    def init_enemy_bullets(self, scenario_info):
        """ 根据想定信息初始化敌方弹药量"""
        for obj in scenario_info['operators']:
            if obj['color'] != self.__color:
                self.enemy_bullets[obj['obj_id']] = obj['remain_bullet_nums']
        for obj in scenario_info['passengers']:
            if obj['color'] != self.__color:
                self.enemy_bullets[obj['obj_id']] = obj['remain_bullet_nums']

    def update_enemy_bullets(self, judge_info):
        """ 根据裁决信息更新敌方弹药量"""
        if judge_info:
            for info in judge_info:
                obj_id = info['att_obj_id']
                enemy_info = self.getEnemyInfoByID(obj_id)
                if enemy_info:
                    if enemy_info['operator']['sub_type'] != BopType.Artillery:
                        if obj_id in self.enemy_bullets.keys():
                            wp_id = info['wp_id']
                            bullet_type = get_weapon(wp_id).type
                            self.enemy_bullets[obj_id][bullet_type] -= 1 if self.enemy_bullets[obj_id][
                                                                                bullet_type] >= 1 else 0
                            # enemy_info['remain_bullet_nums'] = self.enemy_bullets[obj_id]

    def update_enemy_threat(self):
        """更新敌方的威胁态势"""
        n_hexes = self.__map_size['row'] * self.__map_size['col']
        self.enemy_threat = np.zeros((4, n_hexes))
        for enemy_info in self.enemies:
            if enemy_info['operator']['sub_type'] not in [BopType.Artillery, BopType.Drone]:  # 不考虑炮兵和无人机的威胁
                for m in range(4):
                    self.enemy_threat[m] += enemy_info['threat'][m]

    def update_artillery_threat(self, observation):
        # 增加炮兵的威胁，一个炮兵设定为2点的攻击等级
        n_hexes = self.__map_size['row'] * self.__map_size['col']
        self.artillery_threat = np.zeros(n_hexes, dtype=float)
        for jm_info in observation['jm_points']:
            if jm_info['status'] == 0 and jm_info['fly_time'] <= 150:
                cur_pos = jm_info['pos']
                cur_pos_ind = mapid_2_matind(cur_pos, self.__map_size)
                self.artillery_threat[cur_pos_ind] += 1  # 正在飞行的间瞄火力设定为1
            elif jm_info['status'] == 1 and jm_info['boom_time'] <= 300:
                cur_pos = jm_info['pos']
                cur_pos_ind = mapid_2_matind(cur_pos, self.__map_size)
                self.artillery_threat[cur_pos_ind] += 1.  # 正在爆炸的间瞄火力设定为2

    def calc_proximate_cities(self, myAgent):
        map_size = myAgent.get_map_size()
        n_hex = map_size['row'] * map_size['col']
        city_locs = np.array([city['coord'] for city in myAgent.scenario_info[-1]['cities']])
        city_loc_inds = mapid_2_matind(city_locs, map_size)
        dist_matrix = myAgent.my_map['dist_matrix']
        city_dist_matrix = dist_matrix[city_loc_inds]
        city_dist_matrix[city_dist_matrix > 20] = 20
        city_dist_matrix[city_dist_matrix < 3] = 3
        min_dist = np.min(city_dist_matrix, axis=0)
        if np.max(min_dist) == np.min(min_dist):
            min_dist_norm = np.ones(n_hex)
        else:
            min_dist_norm = (np.max(min_dist) - min_dist) / (np.max(min_dist) - np.min(min_dist))
        return min_dist_norm


class EnemyThreat(object):
    def __init__(self, enemy, situation, myAgent):
        self.obj_id = enemy['obj_id']
        self.__color = myAgent.color
        self.__map_size = myAgent.get_map_size()
        self.n_hexes = self.__map_size['row'] * self.__map_size['col']
        self.observation = None  # 对myAgent中的observation作备份
        self.last_observation = myAgent.scenario_info[-1]
        self.proximate_2_cities = situation.proximate_2_cities
        self.max_spread_radius = situation.max_spread_radius
        self.__last_step = -1
        self.enemy_info = {}
        self.init_enemies(enemy, myAgent)
        #  敌人信息：{dict}
        #  {
        #      "obj_id": 算子ID
        #      "operator": 算子属性
        #      "step": 发现算子的时刻
        #      "pos_probs": 位置概率
        #      "pos_update_step": 位置概率的更新时刻
        #      "pos_update_frequency": 位置概率的更新频率
        #      "remain_bullet_nums": 弹药剩余量
        #      "shoot_range": 算子对不同类型目标的射程
        #      "trace": 算子的最近轨迹的记录，只记录最近的3格
        #      "change_flag": 敌算子位置变化的标志
        #      "threat": 算子的威胁，格式同situation.enemy_threat
        #      "spread_flag": 位置扩散的标志，此值为False时，不更新算子位置和威胁信息，标明算子跟踪丢失
        #  }
        self.enemy_info_copy = copy.deepcopy(self.enemy_info)

    def update(self, situation, mutex_basic_situ, mutex_threat_situ, enemy_thread_dict, myAgent):
        # while True:
        while enemy_thread_dict.get(self.obj_id) is not None:
            if situation.situ_observation is None:
                break
            self.observation = situation.situ_observation
            if self.enemy_info['obj_id'] in situation.dead_enemies:
                break
            if self.observation['time']['cur_step'] <= self.__last_step:
                time.sleep(0.05)
                continue
            # print(f'\t\t\tenemy id: {self.enemy_info["obj_id"]} current step：{myAgent.observation["time"]["cur_step"]}')

            # 将传入的进程态势对象situ（基本）属性拷贝出来，用于更新敌方算子威胁
            # === 加锁 - in situation ===
            mutex_basic_situ.acquire()
            enemies = copy.deepcopy(situation.basic_enemies)
            enemy_bullets = copy.deepcopy(situation.enemy_bullets)
            our_oprs = copy.deepcopy(situation.our_oprs)
            # === 释放 - in situation ===
            mutex_basic_situ.release()

            cur_step = self.observation['time']['cur_step']
            cur_enemy_id = self.enemy_info['obj_id']
            # === 更新敌方算子的弹药剩余量和射程 ===
            self.enemy_info['remain_bullet_nums'] = enemy_bullets[cur_enemy_id]
            self.update_shoot_range()
            new_enemy = None
            for enemy in enemies:
                if enemy['obj_id'] == cur_enemy_id:
                    new_enemy = enemy
                    break
            if new_enemy:  # === 更新观察到的敌方算子 ===
                self.enemy_info["operator"] = new_enemy
                self.enemy_info["step"] = cur_step
                self.enemy_info["pos_probs"] = np.zeros(self.n_hexes, dtype=float)
                self.enemy_info["pos_probs"][mapid_2_matind(new_enemy['cur_hex'], self.__map_size)] = 1.
                self.enemy_info["pos_update_step"] = cur_step
                if not self.enemy_info["trace"]:
                    self.enemy_info["trace"].insert(0, (new_enemy['cur_hex'], cur_step))
                else:
                    if new_enemy['cur_hex'] != self.enemy_info["trace"][0][0]:
                        self.enemy_info["trace"].insert(0, (new_enemy['cur_hex'], cur_step))
                        if len(self.enemy_info["trace"]) > 3:
                            del self.enemy_info["trace"][-1]
                if new_enemy['type'] != BopType.Aircraft or new_enemy['sub_type'] != BopType.Artillery:
                    self.enemy_info["spread_flag"] = True  # 开始更新
                self.update_opr_threat(myAgent)
            else:
                # 算子跟踪丢失的判断
                max_time_window = self.max_spread_radius[self.enemy_info['operator']['type']] * (
                            200 / self.enemy_info['operator']['basic_speed'] * 3.6)
                if cur_step - self.enemy_info["step"] >= max_time_window:
                    self.reset_enemy()  # 重置敌方算子
                if self.enemy_info["spread_flag"]:  # === 更新未观察到的敌方算子 ===
                    our_changed_oprs = self.get_our_changed_oprs(our_oprs)
                    if self.enemy_info["step"] != cur_step:
                        if self.check_update_pos_probs(cur_step):
                            self.update_pos_probs(cur_step, myAgent)
                            self.correct_pos_probs_visions(our_oprs, myAgent)
                        elif our_changed_oprs:  # 用我方位置变化的算子，修正敌方算子的位置
                            self.correct_pos_probs_visions(our_changed_oprs, myAgent)
                        if self.enemy_info['change_flag']:
                            self.update_opr_threat(myAgent)

            # === 加锁 out - enemy_threat ===
            mutex_threat_situ.acquire()
            self.enemy_info_copy = copy.deepcopy(self.enemy_info)
            # === 释放 out - enemy_threat ===
            mutex_threat_situ.release()

            # === 记录last observation ===
            self.last_observation = copy.deepcopy(self.observation)
            self.__last_step = self.observation['time']['cur_step']

            # time.sleep(0.05)
        print(f'game over: enemy {self.obj_id} thread exit')

    def init_enemies(self, enemy, myAgent):
        map_size = self.__map_size
        n_hexes = self.n_hexes
        cur_step = 0
        self.enemy_info["obj_id"] = enemy['obj_id']
        self.enemy_info["operator"] = enemy
        self.enemy_info["step"] = cur_step
        self.enemy_info["pos_probs"] = np.zeros(n_hexes, dtype=float)
        self.enemy_info["pos_update_step"] = cur_step
        self.enemy_info["pos_update_frequency"] = 200 / (enemy['basic_speed'] / 3.6)
        self.enemy_info["pos_probs"][mapid_2_matind(enemy['cur_hex'], map_size)] = 1.
        self.enemy_info["remain_bullet_nums"] = enemy['remain_bullet_nums']  # 敌算子弹药剩余量
        self.enemy_info["shoot_range"] = {}
        if enemy['sub_type'] != BopType.Artillery:
            self.update_shoot_range()
        self.enemy_info["trace"] = []
        self.enemy_info["change_flag"] = False  # 敌算子位置变化的标志
        self.enemy_info["threat"] = np.zeros((4, n_hexes))
        self.enemy_info["spread_flag"] = False  # 位置扩散的标志

    def reset_enemy(self):
        map_size = self.__map_size
        n_hexes = self.n_hexes
        self.enemy_info["pos_probs"] = np.zeros(n_hexes, dtype=float)
        self.enemy_info["pos_probs"][mapid_2_matind(self.enemy_info['operator']['cur_hex'], map_size)] = 1.
        self.enemy_info["change_flag"] = False  # 敌算子位置变化的标志
        self.enemy_info["threat"] = np.zeros((4, n_hexes))
        self.enemy_info["spread_flag"] = False  # 位置扩散的标志

    def update_shoot_range(self):
        enemy = self.enemy_info['operator']
        if enemy['sub_type'] != BopType.Artillery:  # 炮兵不更新
            self.enemy_info["shoot_range"][ObservedType.Vehicle] = \
                min(get_shoot_radius(enemy['carry_weapon_ids'], self.enemy_info["remain_bullet_nums"],
                                     BopType.Vehicle),
                    enemy['observe_distance'][ObservedType.Large])
            self.enemy_info["shoot_range"][ObservedType.Infantry] = \
                min(get_shoot_radius(enemy['carry_weapon_ids'], self.enemy_info["remain_bullet_nums"],
                                     BopType.Infantry),
                    enemy['observe_distance'][ObservedType.Medium])
            self.enemy_info["shoot_range"][ObservedType.LargeAircraft] = \
                min(get_shoot_radius(enemy['carry_weapon_ids'], self.enemy_info["remain_bullet_nums"],
                                     BopType.Aircraft),
                    enemy['observe_distance'][ObservedType.Large])
            self.enemy_info["shoot_range"][ObservedType.LittleAircraft] = \
                min(get_shoot_radius(enemy['carry_weapon_ids'], self.enemy_info["remain_bullet_nums"],
                                     BopType.Aircraft),
                    enemy['observe_distance'][ObservedType.Little])

    def update_opr_threat(self, myAgent):
        """更新敌方某一个算子的威胁等级"""
        map_size = myAgent.get_map_size()
        n_hexes = self.n_hexes
        for m in range(4):  # 更新4类目标的威胁
            if m == ObservedType.Vehicle:
                tar_type = BopType.Vehicle
            elif m == ObservedType.Infantry:
                tar_type = BopType.Infantry
            else:
                tar_type = BopType.Aircraft
            # 确定敌方算子的位置
            enemy = self.enemy_info['operator']
            pos_probs = self.enemy_info['pos_probs']
            remain_bullet_nums = self.enemy_info["remain_bullet_nums"]
            weight = 2  # 威胁权重，人为增加坦克权重
            # 确定可威胁的目标区域
            threat_radius = self.enemy_info["shoot_range"][m]
            if threat_radius == 0:
                self.enemy_info['threat'][m] = np.zeros(n_hexes)
            else:
                dist_matrix = myAgent.my_map['dist_matrix']
                att_area = np.arange(n_hexes)[pos_probs > 0]
                tar_area = np.arange(n_hexes)[np.any(dist_matrix[att_area] <= threat_radius, axis=0)]
                threat_levels = calc_threat_levels(myAgent, enemy['carry_weapon_ids'],
                                                   remain_bullet_nums, pos_probs, tar_type,
                                                   tar_locs=tar_area, att_obj_type=enemy['type'],
                                                   att_blood=enemy['blood'], special_shoot=None)
                self.enemy_info['threat'][m] = np.zeros(n_hexes)
                if enemy['sub_type'] == BopType.Tank and m == ObservedType.Vehicle:
                    self.enemy_info['threat'][m][tar_area] = threat_levels * weight
                else:
                    self.enemy_info['threat'][m][tar_area] = threat_levels
                if np.max(pos_probs) > 1 - 1e-8:
                    # 步兵下车时，空中算子增加威胁
                    if m == ObservedType.LargeAircraft:
                        if enemy['sub_type'] == BopType.IFV and enemy['get_off_partner_id']:
                            for launcher_id in enemy['get_off_partner_id']:
                                launcher = myAgent.get_enemy(launcher_id)
                                if launcher['type'] != BopType.Infantry:
                                    continue
                                sup_dist = map_size['row'] + map_size['col']  # 距离的上限
                                extended_att_level = np.zeros(sup_dist)  # 保证所有距离上都可以索引
                                extended_att_level[0:11] = np.array([7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8])
                                cur_loc = mapid_2_matind(enemy['cur_hex'], map_size)
                                att_levels = extended_att_level[dist_matrix[cur_loc]]
                                self.enemy_info['threat'][m] += att_levels
                    if m == ObservedType.LittleAircraft:
                        if enemy['sub_type'] == BopType.IFV and enemy['get_off_partner_id']:
                            for launcher_id in enemy['get_off_partner_id']:
                                launcher = myAgent.get_enemy(launcher_id)
                                if launcher['type'] != BopType.Infantry:
                                    continue
                                sup_dist = map_size['row'] + map_size['col']  # 距离的上限
                                extended_att_level = np.zeros(sup_dist)  # 保证所有距离上都可以索引
                                extended_att_level[0:3] = np.array([7, 7, 7])
                                cur_loc = mapid_2_matind(enemy['cur_hex'], map_size)
                                att_levels = extended_att_level[dist_matrix[cur_loc]]
                                self.enemy_info['threat'][m] += att_levels
        self.enemy_info['change_flag'] = False

    def get_our_changed_oprs(self, our_oprs):
        """输出位置发生变化的我方算子"""
        changed_oprs = []
        for opr in our_oprs:
            last_opr = self.get_last_bop(opr['obj_id'])
            if last_opr:
                if last_opr['cur_hex'] != opr['cur_hex']:
                    changed_oprs.append(opr)
        return changed_oprs

    def check_update_pos_probs(self, cur_step):
        """ 更新敌方算子的位置概率的判断"""
        discover_step = self.enemy_info['step']
        last_update_step = self.enemy_info['pos_update_step']
        update_frequency = self.enemy_info['pos_update_frequency']
        # 判断是否需要更新
        updated_count = (last_update_step - discover_step) // update_frequency  # 已更新次数
        curr_cycle = (cur_step - discover_step) // update_frequency  # 当前处于的更新周期
        if curr_cycle > updated_count:  # 需要更新的依据
            return True
        return False

    def update_pos_probs(self, cur_step, myAgent):
        """ 更新敌方算子的位置概率"""
        enemy = self.enemy_info['operator']
        dist_matrix = myAgent.my_map['dist_matrix']
        if enemy['type'] == BopType.Vehicle:
            cost_matrix = myAgent.my_map['cost_matrix'][0] / 20
        elif enemy['type'] == BopType.Infantry:
            cost_matrix = myAgent.my_map['cost_matrix'][1] / 144
        else:
            cost_matrix = dist_matrix
        pos_probs = self.enemy_info['pos_probs']
        map_size = self.__map_size
        n_hex = self.n_hexes
        cur_hex = enemy['cur_hex']
        cur_hex_ind = mapid_2_matind(cur_hex, map_size)
        # cur_hex_ind = np.argmax(pos_probs)  # 算子的当前位置用最大概率的位置代替
        # 夺控点
        city_locs = np.array([city['coord'] for city in self.observation['cities']])
        city_loc_inds = mapid_2_matind(city_locs, map_size)
        # 算子位置
        pos_locs = np.arange(n_hex)[pos_probs > 0]
        pos_size = pos_locs.size
        # 更新enemy_info['pos_update_step']
        self.enemy_info['pos_update_step'] = cur_step
        # 设定的最大扩散区域
        max_spread_radius = self.max_spread_radius[enemy['type']]
        spread_area = np.arange(n_hex)[dist_matrix[cur_hex_ind] <= max_spread_radius]
        # spread_area_size = spread_area.size
        neighbor_matrix_full = (dist_matrix[pos_locs] <= 1)
        neighbor_matrix_full[np.arange(pos_size), pos_locs] = False
        neighbor_matrix = neighbor_matrix_full[:, spread_area]
        # 计算对夺控点的最大攻击等级
        att_level_2_dist = np.zeros(map_size['row'] + map_size['col'], dtype=float)
        att_level_2_dist[:21] = np.maximum(
            np.maximum(
                integrate_att_level(enemy['carry_weapon_ids'], self.enemy_info['remain_bullet_nums'],
                                    BopType.Infantry)[enemy['blood'] - 1],
                integrate_att_level(enemy['carry_weapon_ids'], self.enemy_info['remain_bullet_nums'], BopType.Vehicle)
            ),
            integrate_att_level(enemy['carry_weapon_ids'], self.enemy_info['remain_bullet_nums'], BopType.Aircraft)
        )  # 21维向量，最大距离20，大于20的距离，攻击等级=0
        max_a_l = np.max(att_level_2_dist)
        # att_level_2_dist = np.concatenate((att_level_2_dist, np.zeros(map_size['row'] + map_size['col'])))
        if abs(max_a_l - 0) < 1e-8:
            max_att_levels_norm = np.ones(pos_size)
        else:
            max_att_levels_norm = np.max(
                att_level_2_dist[dist_matrix[city_loc_inds.reshape(-1, 1), pos_locs.reshape(1, -1)]],
                axis=0) / max_a_l  # 此值越大，向夺控点的靠近速率越小
        # 计算各六角格的静止参数
        lumbda = 2  # 指数参数
        stop_paras = np.maximum(max_att_levels_norm, self.proximate_2_cities[pos_locs]) ** lumbda
        # 计算各六角格的原地静止概率
        min_stop_prob, max_stop_prob = 0.05, 0.8  # 分别以多大的概率停止在原地
        stop_probs = (max_stop_prob - min_stop_prob) * stop_paras + min_stop_prob
        stop_probs_matrix_full = np.zeros((pos_size, n_hex), dtype=float)
        stop_probs_matrix_full[np.arange(pos_size), pos_locs] = stop_probs
        stop_probs_matrix = stop_probs_matrix_full[:, spread_area]
        # 确定扩散中夺控点朝向性参数，=1时表示均匀向6个方向扩散
        max_orient_base = 2.  # 最大朝向夺控点的指数的底数
        orient_bases = (max_orient_base - 1) * stop_paras + 1
        # orient_base_matrix = np.tile(orient_bases.reshape(-1, 1), (1, n_hex))
        # 确定6个邻格的扩散概率
        cost_matrix_used = cost_matrix[pos_locs.reshape(-1, 1), spread_area.reshape(1, -1)]
        min_city_dist = np.min(cost_matrix[city_loc_inds.reshape(-1, 1), spread_area.reshape(1, -1)], axis=0)
        neighbor_dist = np.zeros(neighbor_matrix.shape, dtype=float)
        neighbor_dist[neighbor_matrix] = np.tile(min_city_dist, (pos_size, 1))[neighbor_matrix] + \
                                         cost_matrix_used[neighbor_matrix]
        neighbor_dist[np.logical_not(neighbor_matrix)] = np.nan
        max_neighbor_dist = np.nanmax(neighbor_dist, axis=1)
        neighbor_dist_rect = max_neighbor_dist.reshape(-1, 1) - neighbor_dist
        neighbor_powers = np.power(orient_bases.reshape(-1, 1), neighbor_dist_rect)
        neighbor_probs = neighbor_powers / np.nansum(neighbor_powers, axis=1).reshape(-1, 1)
        # 确定在所有格子上的扩散概率
        spread_probs = stop_probs_matrix + np.nan_to_num((1 - stop_probs).reshape(-1, 1) * neighbor_probs)
        # 根据扩散概率spread_probs，更新位置概率
        self.enemy_info['pos_probs'] = np.zeros(n_hex)
        self.enemy_info['pos_probs'][spread_area] = np.sum(pos_probs[pos_locs].reshape(-1, 1) * spread_probs, axis=0)
        self.enemy_info['change_flag'] = True

    def correct_pos_probs_visions(self, our_oprs, myAgent):
        """根据我方视野更新位置概率"""
        if not our_oprs:  # 我方没有算子，不更新
            return
        if not isinstance(our_oprs, list):
            our_oprs = [our_oprs]
        map_size = self.__map_size
        n_hexes = self.n_hexes
        dist_matrix = myAgent.my_map['dist_matrix']
        terrain_vec = myAgent.my_map['terrain_vec']
        pos_probs = copy.deepcopy(self.enemy_info['pos_probs'])
        pos_locs = np.arange(n_hexes)[pos_probs > 0]
        n_pos_locs = pos_locs.size
        enemy = self.enemy_info['operator']
        # 确定敌方算子的被观察类型
        if enemy['type'] == BopType.Vehicle:
            observed_type = ObservedType.Large
        elif enemy['type'] == BopType.Infantry:
            observed_type = ObservedType.Medium
        elif enemy['sub_type'] == BopType.Helicopter:
            observed_type = ObservedType.Large
        else:
            observed_type = ObservedType.Little
        n_oprs = len(our_oprs)
        vision_radius_vec = np.zeros(n_oprs)
        opr_locs = np.zeros(n_oprs, dtype=int)
        see_matrix = np.zeros((n_oprs, n_pos_locs), dtype=bool)
        for m in range(n_oprs):
            opr = our_oprs[m]
            # 确定视野半径
            vision_radius_vec[m] = opr['observe_distance'][observed_type]
            opr_locs[m] = mapid_2_matind(opr['cur_hex'], map_size)
            if opr['type'] == BopType.Aircraft and enemy['type'] == BopType.Aircraft:
                see_matrix[m, :] = myAgent.my_map['see_matrix'][1][opr_locs[m], pos_locs.reshape(1, -1)]
            elif opr['type'] != BopType.Aircraft and enemy['type'] != BopType.Aircraft:
                see_matrix[m, :] = myAgent.my_map['see_matrix'][0][opr_locs[m], pos_locs.reshape(1, -1)]
            else:
                see_matrix[m, :] = myAgent.my_map['see_matrix'][2][opr_locs[m], pos_locs.reshape(1, -1)]
        dist_matrix_used = dist_matrix[opr_locs.reshape(-1, 1), pos_locs.reshape(1, -1)]
        in_vision_range = (dist_matrix_used <= vision_radius_vec.reshape(-1, 1))  # 有效距离范围的确定
        handle_area = in_vision_range & see_matrix  # 有效距离范围中，可视部分的确定
        rect_probs = np.ones(handle_area.shape, dtype=float)
        rect_probs[handle_area] = 0.2
        good_terrains = (terrain_vec[pos_locs] == TerrainType.Forest) | (terrain_vec[pos_locs] == TerrainType.Resident)
        terrain_rects = np.ones(pos_locs.size, dtype=float)
        terrain_rects[good_terrains] = terrain_rects[good_terrains] * 0.5
        safe_dists = np.outer(vision_radius_vec / 2., terrain_rects)
        see_bool = (dist_matrix_used <= safe_dists)
        rect_probs[see_bool] = 0.
        rect_probs_vec = np.min(rect_probs, axis=0)
        rect_probs_full = np.ones(n_hexes, dtype=float)
        rect_probs_full[pos_locs] = rect_probs_vec
        pos_probs = pos_probs * rect_probs_full
        if np.max(pos_probs) < 1e-8:
            enemy_loc = mapid_2_matind(enemy['cur_hex'], map_size)
            max_spread_radius = self.max_spread_radius[enemy['type']]
            pos_probs = np.zeros(n_hexes, dtype=float)
            max_spread_bool = (dist_matrix[enemy_loc] <= max_spread_radius)
            pos_probs[max_spread_bool] = 1. / np.sum(max_spread_bool + 0)
        else:
            pos_probs = pos_probs / np.sum(pos_probs)
        if np.sum(np.abs(self.enemy_info['pos_probs'] - pos_probs)) > 1e-8:
            self.enemy_info['pos_probs'] = pos_probs
            self.enemy_info['change_flag'] = True

    def get_last_bop(self, obj_id):
        """
        Get bop in my last_observation based on its id.
        """
        for bop in self.last_observation['operators']:
            if obj_id == bop['obj_id']:
                return bop
        for bop in self.last_observation['passengers']:
            if obj_id == bop['obj_id']:
                return bop
        return None
